package com.wumz.spring.write.v2.framework.servlet;

import com.wumz.spring.write.v2.framework.annotation.*;
import com.wumz.spring.write.v2.framework.context.MyApplicationContext;
import com.wumz.spring.write.v2.framework.webmvc.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 委派模式
 * 职责：负责任务调度，请求分发
 * Created by wumz on 2020/9/26 13:50
 */
public class MyDispatcherServlet extends HttpServlet {

    private MyApplicationContext applicationContext;

    private List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();

    private List<MyViewResolver> viewResolvers = new ArrayList<MyViewResolver>();

    private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdapters = new HashMap<MyHandlerMapping, MyHandlerAdapter>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6、委派，根据URL去找到一个对应的Method并通过response返回
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
        }
    }

    /**
     * 根据URL去找到一个对应的Method并通过response返回
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //完成了对HandlerMapping的封装
        //完成了对方法返回值的封装ModelAndView

        //1、通过URL获得一个HandlerMapping
        MyHandlerMapping handler = getHandler(req);
        if(handler == null){
            processDispatchResult(req,resp,new MyModelAndView("404"));
            return;
        }
        //2、根据一个HandlerMaping获得一个HandlerAdapter
        MyHandlerAdapter ha = getHandlerAdapter(handler);

        //3、解析某一个方法的形参和返回值之后，统一封装为ModelAndView对象
        MyModelAndView mv = ha.handler(req,resp,handler);

        // 就把ModelAndView变成一个ViewResolver
        processDispatchResult(req,resp,mv);
    }

    private MyHandlerAdapter getHandlerAdapter(MyHandlerMapping handler) {
        if(this.handlerAdapters.isEmpty()){return null;}
        return this.handlerAdapters.get(handler);
    }

    /**
     * 就把ModelAndView变成一个ViewResolver
     * @param req
     * @param resp
     * @param myModelAndView
     */
    private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MyModelAndView myModelAndView) throws Exception {
        if(null == myModelAndView){return;}
        if(this.viewResolvers.isEmpty()){return;}

        for (MyViewResolver viewResolver : this.viewResolvers) {
            MyView view = viewResolver.resolveViewName(myModelAndView.getViewName());
            //直接往浏览器输出
            view.render(myModelAndView.getModel(),req,resp);
            return;
        }
    }

    private MyHandlerMapping getHandler(HttpServletRequest req) {
        if(this.handlerMappings.isEmpty()){return  null;}
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");

        for (MyHandlerMapping mapping : handlerMappings) {
            Matcher matcher = mapping.getPattern().matcher(url);
            if(!matcher.matches()){continue;}
            return mapping;
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) {
        try {
            //初始化Spring核心IoC容器
            applicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
            //初始化mvc九大组件
            initStrategies(applicationContext);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 初始化mvc九大组件
     * @param context
     */
    private void initStrategies(MyApplicationContext context) {
        //handlerMapping
        initHandlerMappings(context);
        //初始化参数适配器
        initHandlerAdapters(context);
        //初始化视图转换器
        initViewResolvers(context);
    }

    /**
     * 初始化视图转换器
     * @param context
     */
    private void initViewResolvers(MyApplicationContext context) {
        String templateRoot = context.getConfig().getProperty("templateRoot");
        String templateUrl = this.getClass().getClassLoader().getResource(templateRoot).getFile();
        this.viewResolvers.add(new MyViewResolver(templateUrl));
    }

    /**
     * 初始化参数适配器
     * @param context
     */
    private void initHandlerAdapters(MyApplicationContext context) {
        for (MyHandlerMapping handlerMapping : handlerMappings) {
            this.handlerAdapters.put(handlerMapping, new MyHandlerAdapter());
        }
    }

    /**
     * handlerMapping -- url和Method的映射关系
     * @param context
     */
    private void initHandlerMappings(MyApplicationContext context) {
        if (this.applicationContext.getBeanDefinitionCount() == 0){return;}
        for (String beanName : this.applicationContext.getBeanDefinitionNames()) {
            Object instance = applicationContext.getBean(beanName);
            Class<?> clazz = instance.getClass();
            if (!clazz.isAnnotationPresent(MyController.class)){ continue;}
            //相当于提取 class上配置的url
            String baseUrl = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                baseUrl = annotation.value();
            }
            //只获取public的方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)){ continue; }
                //相当于提取方法上面的requestMapping的值
                MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                String regex = ("/" + baseUrl + "/" + annotation.value().replaceAll("\\*",".*")).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                //handlerMapping.put(url,method);
                if (handlerMappings.contains(new MyHandlerMapping(pattern,method,instance))){
                    continue;
                }
                handlerMappings.add(new MyHandlerMapping(pattern,method,instance));
                System.out.println("Mapped : " + regex + "," + method);
            }
        }
    }
}
